#!/usr/bin/env python


# import common stuff
from songanalysiscommon import *

#Unconditional imports
import os
import sys
import signal # this to make signals go to the main thread *always*
import time
import logging


#start conditional imports
def test_extattr():
	# runtime tests
	try:
		import extattr
	except:
		error = "The Smart DJ plug-in requires the python-extattr package to work properly.  Please install it and try again."
		say_sorry(error)
		sys.exit(0)
test_extattr()


def test_commandsplus():
	try:
		import commandsplus
	except:
		error = "The Smart DJ plug-in requires the python-commandsplus package to work properly.  Please install it and try again."
		say_sorry(error)
		sys.exit(0)
test_commandsplus()


def test_songanalysis():
	from Version import Version
	import songanalysiswrapper
	# the songanalysis should spit its version on stdout
	minversion = "0.4.0"
	version = songanalysiswrapper.get_version()
	if not version:
		say_sorry("The Smart DJ plug-in requires the songanalysis program, which has not been installed or a really old version is available.  Please ensure the latest songanalysis is correctly installed and in your PATH.")
		sys.exit(0)
	required = Version(minversion)
	current = Version(version)
	if current >= required: return # this means OKAY
	say_sorry("An old version of the songanalysis program is installed.  The Smart DJ plug-in requires at least version %s."%minversion)
	sys.exit(0)
test_songanalysis()


def test_observable():
	try:
		from Observable import Observable
	except:
		error = "The Smart DJ plug-in requires the python-Observable package to work properly.  Please install it and try again."
		say_sorry(error)
		sys.exit(0)
test_observable()


def test_amarok():
	try:
		import amarok
	except:
		error = "The Smart DJ plug-in requires the python-amarok package to work properly.  Please install it and try again."
		say_sorry(error)
		sys.exit(0)
test_amarok()


songanalysisui = None
def test_ui():
	global songanalysisui
	try:
		import uiqt as ui
		songanalysisui = ui
	except ImportError: pass
	except RuntimeError:
		error = "The Smart DJ plug-in has detected that your PyQt and Qt versions do not match your SIP library, probably because of a faulty SIP installation.  Update Qt, SIP and PyQt to the latest versions and try again."
		say_sorry(error)
		sys.exit(0)
test_ui()


from commandsplus import shell_escape,getstatusoutputerror
from AnalysisProcess import AnalysisProcess
from ContextBrowserUpdater import ContextBrowserUpdater
from SuperDynamicModeMonitor import SuperDynamicModeMonitor
from Observable import Observable
from amarokplus import amaroKProxyPlus
from SignalQueue import SignalQueue,Empty
#end conditional imports


# utility functions

def setup_logging():
	
	logging.getLogger().setLevel(logging.DEBUG)
	formatter = logging.Formatter("%(levelname)s:%(name)s: %(message)s")
	outputFile = file(os.path.expanduser(n_logfile),"a+b",0)
	
	if len(sys.argv) > 1 and sys.argv[1] == "-v":
		handler = logging.StreamHandler(outputFile)
		handler.setFormatter(formatter)
		logging.getLogger().addHandler(handler)
		handler = logging.StreamHandler(sys.stderr)
		handler.setFormatter(formatter)
		logging.getLogger().addHandler(handler)
	else:
		sys.stdout = outputFile
		sys.stderr = outputFile
		handler = logging.StreamHandler(outputFile)
		handler.setFormatter(formatter)
		logging.getLogger().addHandler(handler)

def finish_logging(): logging.shutdown()

logger = logging.getLogger()

# end logging code


# begin config

import pickle
class ConfigObject(dict,Observable):
	
	def __init__(self):
		dict.__init__(self)
		Observable.__init__(self)
		self.filename = n_config
		self.load()
		
	def load(self):
		logger.debug("loading config")
		_config = {}
		try:
			fn = os.path.expanduser(self.filename)
			f = open(fn,"r")
			_config = pickle.load(f)
			f.close()
			logger.debug("loaded config successfully")
		except IOError, e: _config = None
		except EOFError,e: _config = None
		if _config is None:
			logger.debug("could not load config, using defaults")
			_config = dict() ; s = "super_dynamic_mode"
			_config[s] = dict()
			_config[s]["enabled"] = False
			_config[s]["top_choices"] = 1
			_config[s]["upcoming_songs"] = 3
			_config[s]["choice_mechanism"] = LAST_SONG_IN_PLAYLIST
			_config[s]["insert_position"] = LAST_SONG_IN_PLAYLIST
		if not _config["super_dynamic_mode"].has_key("tempo_weight"):
			_config["super_dynamic_mode"]["tempo_weight"] = 1.0
		if not _config["super_dynamic_mode"].has_key("spectrum_weight"):
			_config["super_dynamic_mode"]["spectrum_weight"] = 1.0
		if not _config["super_dynamic_mode"].has_key("source_playlist"):
			_config["super_dynamic_mode"]["source_playlist"] = None
			
		# we butter the loaded config items into us!
		for key,val in _config.iteritems():
			self[key] = val
			
	def save(self):
		logger.debug("saving config")
		
		_config = {}
		for key,val in self.iteritems():
			_config[key] = val
		
		fn = os.path.expanduser(self.filename)
		os.umask(077)
		try:
			f = open(fn,"w")
			pickle.dump(_config,f)
			f.close()
		except:
			logger.exception("could not save config, continuing")
	
	def notifyChanged(self):
		self.save()
		self.broadcastEvent("configChange")

# end config


#UI's
custom_menu_items = ["Find tracks similar to selection...","Edit tempo of selected tracks...","Analyze selected tracks now","Set up Auto DJ mode..."]
	
# main function
def _actual_main():

	# set up signal handlers
	signal_queue = SignalQueue()
	for sig in [signal.SIGINT,signal.SIGTERM]: signal_queue.trap(sig)
	
	# split in two
	pid = os.fork()
	if (pid): # the parent, we go as normal
		logger.debug("Parent process: forked.  PID: %s",os.getpid())
		while True:
			try: sig = signal_queue.get(block=True,timeout=1)
			except Empty:
				pid,status = os.waitpid(pid,os.WNOHANG)
				if pid:
					if os.WCOREDUMP(status): logger.debug("Child core dumped")
					elif os.WIFSIGNALED(status): logger.debug("Child got an unhandled signal %s",os.WTERMSIG(status))
					elif os.WIFEXITED(status): logger.debug("Child returned with return value %s",os.WEXITSTATUS(status))
					else: logger.debug("Child died with status %s",status)
					break
				continue
			logger.debug("Parent process got signal %s, going down and letting child notice on its own",sig)
			break
		logger.debug("Parent process going down")
	
	else: # child, we continue
		logger.debug("Child process: forked.  PID: %s",os.getpid())
	
		logger.info("Smart DJ starting up normally. User interface: %s",songanalysisui)
		
		config = ConfigObject()
		
		amarokObject = amaroKProxyPlus()
		analyzer = AnalysisProcess(amarokObject)
		autoDJMonitor = SuperDynamicModeMonitor(amarokObject,analyzer,config)
		contextBrowserUpdater = ContextBrowserUpdater(amarokObject,analyzer,config)
		
		analyzer.start()
		autoDJMonitor.start()
		contextBrowserUpdater.start()
		amarokObject.startMonitoring()
		
		if songanalysisui:
			logger.debug("initializing user interface")
			songanalysisui.initialize(amarokObject,config,analyzer)
		else:
			passive_popup("You do not have PyQt installed.\nTherefore, the functionality that requires an user interface won't be available.\nPlease install either PyQt soon.")
		
		logger.info("Smart DJ successfully started")
		
		# now, we wait for impending doom
		
		def parentAlive(): return os.getppid() != 1
		
		while parentAlive() and amarokObject.isAlive() and analyzer.isAlive() and \
			 autoDJMonitor.isAlive() and contextBrowserUpdater.isAlive() \
			 and signal_queue.qsize() < 1: time.sleep(1)
		
		if songanalysisui:
			logger.debug("ending user interface")
			songanalysisui.end()
		
		logger.debug("killing context browser monitor")
		contextBrowserUpdater.kill(asynchronous=True)
		logger.debug("killing Auto DJ monitor")
		autoDJMonitor.kill(asynchronous=True)
		logger.debug("killing analyzer")
		analyzer.kill(asynchronous=True)
		
		logger.debug("waiting for context browser monitor")
		contextBrowserUpdater.kill()
		logger.debug("waiting for Auto DJ monitor")
		autoDJMonitor.kill()
		logger.debug("waiting for analyzer")
		analyzer.kill()
		
		config.save()
		
		logger.info("Smart DJ finished")
	
	
def main():
	
	setup_logging()
	all_okay = True
	try: _actual_main()
# 	except SystemExit,e:
# 		if e == 0: sys.exit(0)
# 		else: raise
	except:
		all_okay = False
		logger.exception("An error occurred while starting up.  PID: %s",os.getpid())
		say_sorry("An error took place while starting up the Smart DJ plug-in.  This is not your fault, but a bug in the Smart DJ plug-in.  Please report the contents of the file %s to the Smart DJ plug-in developers." %os.path.expanduser(n_logfile))
	
	finish_logging()
	if not all_okay: sys.exit(2)
	
	
if __name__ == "__main__":
	main()